package org.ourguy.mymoviesandshows.apiclient.trakt;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Stack;

import org.apache.http.HttpResponse;
import org.apache.http.util.EntityUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.ourguy.mymoviesandshows.apiclient.APIException;
import org.ourguy.mymoviesandshows.apiclient.MixedJsonAPIClient;
import org.ourguy.mymoviesandshows.datamodel.Episode;
import org.ourguy.mymoviesandshows.datamodel.ITitle;
import org.ourguy.mymoviesandshows.datamodel.Movie;
import org.ourguy.mymoviesandshows.datamodel.Season;
import org.ourguy.mymoviesandshows.datamodel.SeasonInfo;
import org.ourguy.mymoviesandshows.datamodel.Series;
import org.ourguy.mymoviesandshows.datamodel.Trailer;
import org.ourguy.mymoviesandshows.httpclient.HTTPClientException;
import org.ourguy.mymoviesandshows.httpclient.trakt.TraktRESTClient;

/**
 * A Client for the Trakt.tv API
 * @author bordjukov
 * @version $Revision: 94 $
 */
public class TraktClient extends MixedJsonAPIClient {
	
	/**
	 * The log tag for the API Client. To be used in Log.*(tag, message)
	 */
	private static final String logTag = "TraktClient";
	
	/**
	 * The REST Client used to interact with the HTTP Server of the API
	 */
	private static final TraktRESTClient httpClient = new TraktRESTClient();
	
	/**
	 * Small poster image suffix
	 * @see http://trakt.tv/api-docs/images
	 */
	private static final String SMALLPOSTER = "-138.jpg";
	
	/**
	 * Small fan art image suffix
	 * @see http://trakt.tv/api-docs/images
	 * Use {@link TraktClient#getSeriesSummary(String)} and
	 */
	private static final String SMALLFANART = "-218.jpg";
	
	/**
	 * Small episode image suffix
	 * @see http://trakt.tv/api-docs/images
	 */
	private static final String SMALLEPISODEART = "-218.jpg";

	/**
	 * Get a list of the top 10 trending TV Shows
	 * @see http://trakt.tv/api-docs/shows-trending
	 */
	public ArrayList<ITitle> getTrendingSeries() throws APIException {
		return getTrendingSeries(10);
	}
	
	/**
	 * @see http://trakt.tv/api-docs/search-movies
	 */
	public Hashtable<String, String> autoCompleteMovie(String q)
			throws APIException {
		final String method = "search/movies";
		Hashtable<String, String> result;
		
		try {
			JSONArray jArray = getJSONArray(httpClient.get(method, q));
			
			android.util.Log.v(logTag, "Got list of movies matching the query" +
					" "+q+" and parsed it into a JSON Array.");
			result = titlesFromJSONArray(jArray);
			
		} catch (HTTPClientException e) {
			throw new APIException("An HTTPClientException occured while" +
					" attempting to get the list of movies.", e);
		}
		
		return result;
	}
	
	/**
	 * @see http://trakt.tv/api-docs/search-shows
	 */
	public Hashtable<String, String> autoCompleteSeries(String q)
			throws APIException {
		final String method = "search/shows";
		Hashtable<String, String> result;
		
		try {
			JSONArray jArray = getJSONArray(httpClient.get(method, q));
			
			android.util.Log.v(logTag, "Got list of series matching the query" +
					" "+q+" and parsed it into a JSON Array.");
			result = titlesFromJSONArray(jArray);
			
		} catch (HTTPClientException e) {
			throw new APIException("An HTTPClientException occured while" +
					" attempting to get the list of series.", e);
		}
		
		return result;
	}
	
	/**
	 * @see #autoCompleteSeries(String)
	 * @see #autoCompleteMovie(String)
	 */
	public Hashtable<String, String> autoCompleteTitle(String q)
			throws APIException {
		Hashtable<String, String> result = new Hashtable<String, String>();
		result.putAll(autoCompleteSeries(q));
		result.putAll(autoCompleteMovie(q));
		return result;
	}
	
	/**
	 * Get a list of the trending TV Shows
	 * @param limit how many do we want at most?
	 * @return The list
	 * @throws APIException
	 */
	public ArrayList<ITitle> getTrendingSeries(int limit) throws APIException {
		
		String method = "shows/trending";
		ArrayList<ITitle> result = new ArrayList<ITitle>();
		
		try {
			JSONArray jArray = getJSONArray(httpClient.get(method));
			
			android.util.Log.v(logTag, "Got list of trending series and " +
					"parsed it into a JSON Array.");
			
			int length = jArray.length();
			
			for (int i = 0; i < length && i <= limit; i++){
				JSONObject jObject = jArray.getJSONObject(i);
				result.add(seriesFromJSONObject(jObject));
			}
			
			return result;
		} catch (HTTPClientException e) {
			throw new APIException("An HTTPClientException occured while" +
					" attempting to get the list of trending series.", e);
		} catch (JSONException e) {
			throw new APIException("A JSON exception occured while attempting" +
					" to get the list of trending series.", e);
		}
	}

	/**
	 * Get a list of the top 10 trending movies
	 * @see http://trakt.tv/api-docs/movies-trending
	 */
	public ArrayList<Movie> getTrendingMovies() throws APIException {
		return getTrendingMovies(10);
	}

	/**
	 * Get a list of the top N trending movies
	 * @param limit List size
	 * @return The list itself
	 * @throws APIException in case something goes wrong
	 * @see http://trakt.tv/api-docs/movies-trending
	 */
	public ArrayList<Movie> getTrendingMovies(int limit) throws APIException {
		
		String method = "movies/trending";
		ArrayList<Movie> result = new ArrayList<Movie>();
		
		try {
			JSONArray jArray = getJSONArray(httpClient.get(method));
			
			android.util.Log.v(logTag, "Got list of trending movies and" +
					"parsed it into a JSON Array.");
			
			int length = jArray.length();
			
			for (int i = 0; i < length && i <= limit; i++){
				JSONObject jObject = jArray.getJSONObject(i);
				result.add(movieFromJSONObject(jObject));
			}
			
		} catch (HTTPClientException e) {
			throw new APIException("An HTTPClientException occured while" +
					" attempting to get the list of trending movies.", e);
		} catch (JSONException e) {
			throw new APIException("A JSON exception occured while attempting" +
					" to get the list of trending movies.", e);
		}
		
		return result;
	}

	/**
	 * @see TraktClient#getTrendingSeries() 
	 * @see TraktClient#getTrendingMovies()
	 */
	//TODO sort the titles somehow...
	public ArrayList<ITitle> getTrendingTitles() throws APIException {
		ArrayList<ITitle> result = new ArrayList<ITitle>();
		
		result.addAll(getTrendingMovies());
		result.addAll(getTrendingSeries());
		
		return result;
	}

	/**
	 * @see http://trakt.tv/api-docs/search-shows
	 */
	public ArrayList<ITitle> searchSeries(String query) throws APIException {
		
		String method = "search/shows";
		ArrayList<ITitle> result = new ArrayList<ITitle>();
		query = makeSearchQuery(query);
		
		try {
			JSONArray jArray = getJSONArray(httpClient.get(method, query));
			
			android.util.Log.v(logTag, "Got list of series matching the query" +
					"and parsed it into a JSON Array.");
			
			int length = jArray.length();
			
			for (int i = 0; i < length; i++){
				JSONObject jObject = jArray.getJSONObject(i);
				result.add(seriesFromJSONObject(jObject));
			}
		} catch (JSONException e) {
			throw new APIException("A JSON exception occured while attempting" +
					" to search for a TV show.", e);
		} catch (HTTPClientException e) {
			throw new APIException("An HTTP exception occured while " +
					"attempting to search for a TV show.", e);
		}
		return result;
	}

	/**
	 * @see http://trakt.tv/api-docs/search-movies
	 */
	public ArrayList<Movie> searchMovies(String query) throws APIException {
		
		String method = "search/movies";
		ArrayList<Movie> result = new ArrayList<Movie>();
		query = makeSearchQuery(query);
		
		try {
			JSONArray jArray = getJSONArray(httpClient.get(method, query));
			
			android.util.Log.v(logTag, "Got list of movies matching the query" +
					" and parsed it into a JSON Array.");
			
			int length = jArray.length();
			
			for (int i = 0; i < length; i++){
				JSONObject jObject = jArray.getJSONObject(i);
				result.add(movieFromJSONObject(jObject));
			}
		} catch (JSONException e) {
			throw new APIException("A JSON exception occured while attempting" +
					" to search for a Movie.", e);
		} catch (HTTPClientException e) {
			throw new APIException("An HTTP exception occured while " +
					"attempting to search for a Movie.", e);
		}
		
		return result;
	}

	/**
	 * Search for a specific title (in both TV shows and Movies)
	 * @see TraktClient#searchSeries(String)
	 * @see TraktClient#searchMovies(String)
	 */
	public ArrayList<ITitle> searchTitles(String query) throws APIException {
		ArrayList<ITitle> result = new ArrayList<ITitle>();
		
		result.addAll(searchMovies(query));
		result.addAll(searchSeries(query));
		Collections.sort(result);
		
		return result;
	}

	public ArrayList<Trailer> searchTrailer(String query) throws APIException {
		// TODO Auto-generated method stub
		return null;
	}

	/**
	 * @see http://trakt.tv/api-docs/series-summary
	 */
	public Series getSeriesSummary(String id) throws APIException {
		final String method = "show/summary";
		JSONObject jObject;
		
		try {
			jObject = getJSONObject(httpClient.get(method, id));
		} catch (HTTPClientException e) {
			throw new APIException("An exception occured while attempting to" +
					"get the summary of " + id, e);
		}
		
		return seriesFromJSONObject(jObject);
	}

	/**
	 * @see http://trakt.tv/api-docs/movie-summary
	 */
	public Movie getMovieSummary(String id) throws APIException {
		final String method = "movie/summary";
		JSONObject jObject;
		
		try {
			jObject = getJSONObject(httpClient.get(method, id));
		} catch (HTTPClientException e) {
			throw new APIException("An exception occured while attempting to " +
					"get the summary of "+id, e);
		}
		
		return movieFromJSONObject(jObject);
	}

	/**
	 * @deprecated Please do not use this method :)
	 */
	public ITitle getTitleSummary(String id) throws APIException {
		ITitle result;
		
		try {
			result = getSeriesSummary(id);
		} catch (APIException e) {
			try {
				result = getMovieSummary(id);
			} catch (APIException f) {
				throw new APIException("An error occured while attempting to" +
						"get the summary information of " + id, f);
			}
		}
		
		return result;
	}
	
	/**
	 * @see http://trakt.tv/api-docs/show-seasons
	 */
	public int getSeasonCount(String id) throws APIException {
		
		String method = "show/seasons";
		
		try {
			JSONArray jArray = getJSONArray(httpClient.get(method, id));
			return jArray.length();
		} catch (HTTPClientException e) {
			throw new APIException("An exception occured while attempting to" +
					"count the number of seasons of "+id, e);
		}
	}
	
	/**
	 * @see http://trakt.tv/api-docs/show-seasons
	 */
	public ArrayList<SeasonInfo> getSeasonInfo(String id) throws APIException {
		final String method = "show/seasons";
		ArrayList<SeasonInfo> result;
		JSONArray jArray;
		
		try {
			jArray = getJSONArray(httpClient.get(method, id));
			android.util.Log.v(logTag, "Got list of seasons for " + id +
					"and parsed it into a JSON Array.");
		} catch (HTTPClientException e) {
			throw new APIException("An HTTP Client exception occured while" +
					"attempting to populate a list of seasons for " + id, e);
		}
		
		if (jArray.length() == 0)
			throw new APIException("The series has no seasons?..");
		
		result = seasonListFromJSONArray(jArray);
		Collections.sort(result);
		return result;
	}
	
	/**
	 * @see #getSeasonInfo(String)
	 */
	public SeasonInfo getSeasonInfo(String id, int index)
			throws APIException {
		ArrayList<SeasonInfo> si = getSeasonInfo(id);
		Iterator<SeasonInfo> i = si.iterator();
		
		while(i.hasNext()) {
			SeasonInfo s = i.next();
			if (s.getNumber() == index)
				return s;
		}
		throw new APIException("No such season");
	}

	/**
	 * @see http://trakt.tv/api-docs/show-seasons
	 */
	public ArrayList<Season> getSeasons(String id) throws APIException {

		ArrayList<Season> result = new ArrayList<Season>();
		
		Iterator<SeasonInfo> i = getSeasonInfo(id).iterator();
		while(i.hasNext()) {
			SeasonInfo si = i.next();
			result.add(new Season(si, getEpisodeList(id, si.getNumber())));
		}
		
		Collections.sort(result);
		return result;
	}

	/**
	 * @see http://trakt.tv/api-docs/show-seasons
	 */
	public Season getSeason(String id, int index) throws APIException {
		Season result;
		
		try {
			SeasonInfo si = getSeasonInfo(id, index);
			ArrayList<Episode> ep = getEpisodeList(id, index);
			result = new Season(si, ep);
		} catch (Exception e) {
			throw new APIException("An exception occured while attempting to" +
					"get a season of a TV show.", e);
		}
		
		return result;
	}

	/**
	 * @see http://trakt.tv/api-docs/show-season
	 */
	public Episode getEpisode(String id, int seasonNumber, int episodeNumber) 
			throws APIException {
		
		final String method = "show/season";
		
		try {
			JSONArray jArray = getJSONArray(httpClient.get(method, id,
					String.valueOf(seasonNumber)));
			
			int length = jArray.length();
			if (jArray.length() == 0)
				throw new APIException("No episodes in season " +seasonNumber+ 
						" of " + id +"?...");

			for (int i = 0; i < length; i++) {
				JSONObject j = jArray.getJSONObject(i);
				if(j.getInt("number") == episodeNumber)
					return episodeFromJSONObject(j, id); 
			}
			throw new APIException("No episode "+episodeNumber+" in "+id+
					", season " +seasonNumber);
		} catch (HTTPClientException e) {
			throw new APIException("An HTTP Client exception occured while" +
					"attempting to populate a list of episodes for season "
					+seasonNumber+" of " + id, e);
		} catch (JSONException e) {
			throw new APIException("A JSON exception occured while" +
					"attempting to populate a list of episodes for season "
					+seasonNumber+" of " + id, e);
		}
	}
	
	/**
	 * @see http://trakt.tv/api-docs/show-season
	 */
	public ArrayList<Episode> getEpisodeList(String id, int index) 
			throws APIException {
		
		final String method = "show/season";
		ArrayList<Episode> result;
		
		try {
			result = new ArrayList<Episode>();
			JSONArray jArray = getJSONArray(httpClient.get(method, id,
					String.valueOf(index)));
			
			int length = jArray.length();
			if (jArray.length() == 0)
				throw new APIException("No episodes in season " +index+ 
						" of " + id +"?...");

			for (int i = 0; i < length; i++)
				result.add(episodeFromJSONObject(jArray.getJSONObject(i), id));

		} catch (HTTPClientException e) {
			throw new APIException("An HTTP Client exception occured while" +
					"attempting to populate a list of episodes for season "
					+index+" of " + id, e);
		} catch (JSONException e) {
			throw new APIException("A JSON exception occured while" +
					"attempting to populate a list of episodes for season "
					+index+" of " + id, e);
		}
		Collections.sort(result);
		return result;
	}
	
	/**
	 * @see #getSeasonInfo(String)
	 * @see #getEpisodeList(String, int)
	 */
	public ArrayList<Episode> getFutureEpisodeList(String id)
			throws APIException {
		ArrayList<Episode> result = new ArrayList<Episode>();
		
		Date currentDate = new Date();
		
		// We always have at least one element in here due to a
		// check in #getSeasonInfo
		Stack<SeasonInfo> si = new Stack<SeasonInfo>();
		si.addAll(getSeasonInfo(id));
		
		SeasonInfo last;
		ArrayList<Episode> future;
		Iterator<Episode> i;
		
		// The algorithm is as follows:
		// 1. pop the last season, check the date of its episodes,
		//	  add the future ones to the future list
		// 2. if the list of seasons is not empty and all of the last season's
		//	  episodes are in the future, go to 1, else break
		do {
			last = si.pop();
			future = new ArrayList<Episode>();
			
			i = getEpisodeList(id, last.getNumber()).iterator();
			while(i.hasNext()) {
				Episode ep = i.next();
				if (ep.getFirstAired().after(currentDate))
					future.add(ep);
			}
			
			result.addAll(future);
		} while(!si.isEmpty() && last.getEpisodeCount() == future.size());
		
		//TODO sort the episodes only by air date!!!
		return result;
	}

	/**
	 * @see http://trakt.tv/api-docs/show-season
	 */
	public int getEpisodeCount(String id, int season) throws APIException {
		
		String method = "show/season";
		
		try {
			JSONArray jArray = getJSONArray(httpClient.get(method, id,
					Integer.toString(season)));
			return jArray.length();
		} catch (HTTPClientException e) {
			throw new APIException("An exception occured while attempting to" +
					"count the number of seasons of "+id, e);
		}
	}
	
	/**
	 * @see #getSeasonInfo(String, int)
	 */
	public int getLastSeasonIndex(String id) throws APIException {
		ArrayList<SeasonInfo> si = getSeasonInfo(id);
		return si.get(si.size() - 1).getNumber();
	}
	
	/**
	 * @see #getEpisodeCount(String, int)
	 */
	public int getLastEpisodeIndex(String id, int season) throws APIException {
		return getEpisodeCount(id, season) - 1;
	}

	/**
	 * @see #getLastSeasonIndex(String)
	 */
	public Season getLastSeason(String id) throws APIException {
		return getSeason(id, getLastSeasonIndex(id));
	}
	
	/**
	 * @see #getEpisodeList(String, int)
	 */
	public Episode getLastEpisode(String id, int season) throws APIException {
		ArrayList<Episode> ep = getEpisodeList(id, season);
		return ep.get(ep.size() - 1);
	}

	/**
	 * Create a new Series object by given JSON Object
	 * @param jObject
	 * @return the list of series
	 * @throws JSONException
	 * @throws MalformedURLException
	 */
	private Series seriesFromJSONObject(JSONObject jObject)
			throws APIException {
		
		Series s;
		
		try {
			// Populating the Series object with data from the API
			s = new Series(jObject.getString("tvdb_id"),
					jObject.getString("title"),	jObject.getString("overview"));
			s.setReleaseDate(new Date(jObject.getLong("first_aired")*1000));
			s.setExternalLink(new URL(jObject.getString("url")));
			s.setCountry(jObject.optString("country"));
			s.setImdbId(jObject.getString("imdb_id"));
			s.setAirDay(jObject.getString("air_day"));
			s.setAirTime(jObject.getString("air_time"));
			s.setGenre(getGenresFromJSONArray(jObject.getJSONArray("genres")));
			s.setRating((float) 
					jObject.getJSONObject("ratings").getInt("percentage")/10);
			
			// Setting the Cover Art if it is available.
			if(jObject.has("images"))
				s.setCoverArt(imageFromJSONObject(jObject
						.getJSONObject("images")));
			
			s.setRuntime(jObject.getInt("runtime"));
			s.setNetwork(jObject.optString("network"));
			//TODO add certification
	
		} catch (JSONException e) {
			throw new APIException("A JSON Exception occured while attempting" +
					" to create a Series object from a JSON Object.", e);
		} catch (MalformedURLException e) {
			throw new APIException("A Malformed URL exception occured while " +
					"attempting to create a Series object from" +
					" a JSON Object.", e);
		}
		
		return s;
	}
	
	private String[] getGenresFromJSONArray(JSONArray jArray) throws APIException {
		ArrayList<String> genres = new ArrayList<String>();
		try {
			int length = jArray.length();
			for (int i = 0; i < length; i++) {
				genres.add(jArray.getString(i));
			}
		} catch (JSONException e) {
			throw new APIException("A JSON Exception occured while attempting" +
					" to parse genres", e);
		}
		return genres.toArray(new String[genres.size()]);
	}
	
	/**
	 * Populate a list of SeasonInfo elements from a JSON Array
	 * @param jArray the JSON array we have
	 * @return the populated list
	 * @throws APIException
	 */
	private ArrayList<SeasonInfo> seasonListFromJSONArray(JSONArray jArray) 
			throws APIException {
		
		ArrayList<SeasonInfo> result = new ArrayList<SeasonInfo>();
	
		int length = jArray.length();
		try {
			for (int i = 0; i < length; i++){
				JSONObject j = jArray.getJSONObject(i);
				
				SeasonInfo s = new SeasonInfo(j.getInt("season"));
				s.setEpisodeCount(j.getInt("episodes"));
				s.setURL(j.getString("url"));
				if (j.has("images"))
					s.setImage(imageFromJSONObject(j.getJSONObject("images")));
				
				result.add(s);
			}
		} catch (JSONException e) {
			throw new APIException("A JSON exception occured while attempting" +
					" to populate a season info list from a JSON array", e);
		} catch (MalformedURLException e) {
			throw new APIException("A Malformed URL exception occured while" +
					" attempting to populate a season info list from a" +
					" JSON array", e);
		}
		
		Collections.sort(result);
		return result;
	}
	
	/**
	 * Populate an Episode object from a given JSON object
	 * @param j the given JSON object
	 * @return the populated Episode object
	 * @throws APIException
	 */
	private Episode episodeFromJSONObject(JSONObject j, String seriesId)
			throws APIException {
		
		Episode ep;
		
		try {
			ep = new Episode(j.getInt("number"), 
					j.getInt("season"), seriesId, j.getString("title"));
			ep.setOverview(j.optString("overview"));
			ep.setFirstAired(new Date(j.getLong("first_aired")*1000));
			ep.setUrl(new URL(j.getString("url")));
			ep.setImage(j.getJSONObject("images").optString("screen"));
		} catch (JSONException e) {
			throw new APIException("A JSON Exception occured while attempting" +
					" to create an Episode object from a JSON Object.", e);
		} catch (MalformedURLException e) {
			throw new APIException("A Malformed URL exception occured while " +
					"attempting to create an Episode object from" +
					" a JSON Object.", e);
		}
		
		return ep;
	}

	/**
	 * Create a Movie object from a given JSON Object
	 * @param jObject
	 * @return the Movie
	 * @throws APIException
	 */
	private Movie movieFromJSONObject(JSONObject jObject) throws APIException {
		Movie m;
		
		try {
			m = new Movie(jObject.getString("tmdb_id"),
					jObject.getString("title"), jObject.getString("overview"));
			m.setReleaseDate(new Date(jObject.getLong("released")*1000));
			m.setExternalLink(new URL(jObject.getString("url")));
			m.setImdbId(jObject.getString("imdb_id"));
			m.setGenre(getGenresFromJSONArray(jObject.getJSONArray("genres")));
			m.setRating((float) 
					jObject.getJSONObject("ratings").getInt("percentage")/10);
			
			if (jObject.has("images"))
				m.setCoverArt(imageFromJSONObject(jObject
						.getJSONObject("images")));
			
			m.setRuntime(jObject.getInt("runtime"));
		} catch (Exception e) {
			throw new APIException("An exception occured while attempting to " +
					"parse a JSON Object to a Movie object", e);
		}
		
		return m;
	}
	
	/**
	 * Get an URL to a poster, fanart or screenshot from a given JSON object
	 * @param j the given JSON object
	 * @return the URL to the image
	 * @throws APIException
	 */
	private String imageFromJSONObject(JSONObject j) throws APIException {
		String result;
		
		try {
			if (j.has("poster"))
				result = j.getString("poster").replaceAll("\\.jpg$",
						SMALLPOSTER);
			else if (j.has("fanart"))
				result = j.getString("fanart").replaceAll("\\.jpg$",
						SMALLFANART);
			else if (j.has("screen"))
				result = j.getString("fanart").replaceAll("\\.jpg$",
						SMALLEPISODEART);
			else
				result = "";
		} catch (JSONException e) {
			throw new APIException("An exception occured while attempting to " +
					"choose a cover art URL from a JSON object", e);
		}
		return result;
	}
	
	/**
	 * Return a hashtable of the kind apiId->title for the given array
	 * @param jArray the given array
	 * @return the formulated hashtable
	 * @throws APIException
	 */
	private Hashtable<String, String> titlesFromJSONArray(JSONArray jArray)
			throws APIException {
		Hashtable<String, String> result = new Hashtable<String, String>();
		
		int length = jArray.length();
		try {
			for (int i = 0; i < length; i++){
				JSONObject j = jArray.getJSONObject(i);
				
				String title = j.getString("title");
				
				String apiId;
				if(j.has("tvdb_id"))
					apiId=j.getString("tvdb_id");
				else if (j.has("tmdb_id"))
					apiId=j.getString("tmdb_id");
				else
					throw new APIException("The title does not have an ID");
				
				result.put(apiId, title);
			}
		} catch (JSONException e) {
			throw new APIException("A JSON exception occured while attempting" +
					" to populate a season info list from a JSON array", e);
		}
	
		return result;
	}

	/**
	 * Parse an HTTP Response to a JSON Array
	 * @param response the HTTP Response to parse
	 * @return the corresponding JSON array
	 * @throws APIException if something goes wrong
	 */
	private JSONArray getJSONArray(HttpResponse response) throws APIException {
		try {
			return new JSONArray(EntityUtils.toString(response.getEntity()));
		} catch (Exception e) {
			throw new APIException("An exception occured while attempting to" +
        			"parse an HTTP response to a JSON Object", e);
		}
	}
	
	/**
	 * Parse an HTTP Response to a JSON Object
	 * @param response the HTTP Response to parse
	 * @return the corresponding JSON object
	 * @throws APIException if something goes wrong
	 */
	private JSONObject getJSONObject(HttpResponse response) throws APIException{
		try {
			return new JSONObject(EntityUtils.toString(response.getEntity()));
		} catch (Exception e) {
			throw new APIException("An exception occured while attempting to" +
        			"parse an HTTP response to a JSON Object", e);
		}
	}

	/**
	 * Prepare a query for search in Trakt API. An undocumented feature of the
	 * Trakt TV api - searching for Blade_Runner returns only blade runner,
	 * while searching for Blade Runner returns all results that have blade or
	 * runner in them.
	 * @param query the string we are normalizing
	 * @return the normalized string
	 */
	private String makeSearchQuery(String query) {
		return query.toLowerCase().replace(" ", "_");
	}
}
